package idgenerator

import (
	"hash/fnv"
	"log"
	"sync"
	"time"
)

const (
	epoch0 int64 = 1288834974657
	timeLeft uint8 = 22
	nodeLeft uint8 = 10
	maxSequence int64 = -1 ^ (-1 << uint64(nodeLeft))
	nodeMask int64 = -1 ^ (-1 << uint64(timeLeft-nodeLeft))
)

type IDGenerator struct {
	mu *sync.Mutex
	lastStamp int64
	nodeID int64
	sequence int64
	epoch time.Time
}

func MakeGenerator(node string) *IDGenerator {
	fnv64 := fnv.New64()
	_, _ = fnv64.Write([]byte(node))
	nodeID := int64(fnv64.Sum64()) & nodeMask
	currTime := time.Now()
	epoch := currTime.Add(time.Unix(epoch0 / 1e3, (epoch0 % 1e3) * 1e6).Sub(currTime))
	return &IDGenerator{
		mu:        &sync.Mutex{},
		lastStamp: -1,
		nodeID:    nodeID,
		sequence:  1,
		epoch:     epoch,
	}
}

// NextID returns next unique ID
func (g *IDGenerator) NextID() int64 {
	g.mu.Lock()
	defer g.mu.Unlock()

	timestamp := time.Since(g.epoch).Nanoseconds() / 1e6
	if timestamp < g.lastStamp {
		log.Fatal("can not generate id")
	}
	if g.lastStamp == timestamp {
		g.sequence = (g.sequence + 1) & maxSequence
		if g.sequence == 0 {
			for timestamp <= g.lastStamp {
				timestamp = time.Since(g.epoch).Nanoseconds() / 1e6
			}
		}
	}else {
		g.sequence = 0
	}
	g.lastStamp = timestamp
	id := (timestamp << timeLeft) | (g.nodeID << nodeLeft) | g.sequence
	return id
}






















